(ns fincal.core
(:gen-class)
  (:import
    (java.awt GridBagLayout Insets Dimension Color Font)
    (javax.swing.event DocumentListener)
    (javax.swing.table DefaultTableModel)
    (javax.swing JMenuBar JMenu JMenuItem JButton JLabel JTable JPanel JFrame JOptionPane JTextField)))

(defmacro with-action [component event & body]
  `(. ~component addActionListener
      (proxy [java.awt.event.ActionListener] []
        (actionPerformed [~event] ~@body))))

(def am-ref (ref 0))
(def rt-ref (ref 0))
(def nm-ref (ref 0))
(def tm-ref (ref 0))

(defn update-ref 
"It sets the state of the reference to be the value, and then returns the value."
[a b]
(dosync (ref-set a b))
)

(defn intrest-basic 
"Calcutes in basic intrest increase."
[a b] 
(if (< b 1)
(+ 1 (/ a 100))
(+ 1 (/ (/ a 100) b))))

(defn power
"Calculates a number to the power of a provided exponent. Note, that the exponent must be an integer."
[numb exponent]
(if (zero? exponent)
1
(cond
(pos? exponent) (* numb (power numb (- exponent 1)))
(neg? exponent) (/ (* numb (power numb (- (* -1 exponent) 1)))))))

(defn comp-intrest
"Regular Compound Interest Total"
[p0 r0 n0 t0]
(cond 
(= n0 12) (* p0 (power (intrest-basic r0 n0) t0))
(or (= n0 1)(= n0 2)(= n0 4) (= n0 365)) (* p0 (power (intrest-basic r0 n0) (* n0 t0)))
)
)

(defn comp-profit
"Regular Compound Interest Profit"
[p1 r1 n1 t1]
(- (comp-intrest p1 r1 n1 t1) p1)
)

(defn comp-increase
"Regular Compound Interest percent Increase"
[p2 r2 n2 t2]
(* (/ (comp-profit p2 r2 n2 t2) p2) 100)
)

(defn comp-profit-diff
"Regular Compound Interest Profit diffrence between the current year and the previous year."
[p1 r1 n1 t1]
(- (comp-profit p1 r1 n1 t1) (comp-profit p1 r1 n1 (dec t1)))
)

(defn comp-increase-diff
"Regular Compound Interest percent Increase diffrence between the current year and the previous year."
[p1 r1 n1 t1]
(- (comp-increase p1 r1 n1 t1) (comp-increase p1 r1 n1 (dec t1)))
)

(defn parse
"It takes a String typed by the user and converts it to a number. If the user types an invalid number, function returns nil, but it can handle extra spaces"
 [s]
  (try (Double/parseDouble (.trim s))
       (catch NumberFormatException e nil)))

(defn reverse-parse
"It takes a number, rounds it to the nearest Integer and returns a String."
 [n]
  (str (Math/round (float n))))

(defn message-box
  "message-box to display in the application."
  [mes]
(JOptionPane/showMessageDialog nil (str mes))
)

(defn num-check
"Checks if the text inside the textfield ia a positive number."
 [nm]
(and (number? (parse (.getText nm))) (pos? (parse (.getText nm))))
)

(defn tab-size
"Determines the dimensions of a table and data inside that table."
 [dm rw cl1 cl2 cl3 cl4 cl5 cl6 rw1]
  (doto dm 
     (.setNumRows rw) 
     (.setColumnCount 6)
     (.setValueAt cl1 rw1 0)
     (.fireTableCellUpdated rw1 0)
     (.setValueAt cl2 rw1 1)
     (.fireTableCellUpdated rw1 1)
     (.setValueAt cl3 rw1 2)
     (.fireTableCellUpdated rw1 2)
     (.setValueAt cl4 rw1 3)
     (.fireTableCellUpdated rw1 3)
     (.setValueAt cl5 rw1 4)
     (.fireTableCellUpdated rw1 4)
     (.setValueAt cl6 rw1 5)
     (.fireTableCellUpdated rw1 5)
  )
)

(defn total-message
  "Display the final result of the calculation and the annual increase inside the table, if all the input data is correct. If not, the table is reset and there are not results."
  [dm p r n t tl pl rn]
 (cond 
   (and (num-check p) (num-check r) (number? (parse (.getText n))) (num-check t) (zero? (rem (* 10 (parse (.getText t))) 10)) (or (= (parse (.getText n)) 1) (= (parse (.getText n)) 2) (= (parse (.getText n)) 4) (= (parse (.getText n)) 365) (= (parse (.getText n)) 12))) (do 
    (.setText tl (str "" (format "%.3f" (comp-intrest (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) (parse (.getText t)))) ""))
    (.setText pl (str "" (format "%.3f" (comp-profit (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) (parse (.getText t)))) " (" (format "%.2f" (comp-increase (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) (parse (.getText t)))) " %)"))
    (.setForeground tl (Color. 15 15 246))
    (.setForeground pl (Color. 15 15 246))
    (update-ref am-ref (parse (.getText p)))
    (update-ref rt-ref (parse (.getText r)))
    (update-ref nm-ref (parse (.getText n)))
    (update-ref tm-ref (parse (.getText t)))
 (doseq [rn (range 1 (inc (parse (.getText t))))] 
   (tab-size dm (inc (parse (.getText t))) 
      (reverse-parse rn) 
      (format "%.3f" (comp-intrest (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) rn))
      (format "%.3f" (comp-profit (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) rn))
      (format "%.2f" (comp-increase (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) rn))
      (format "%.3f" (comp-profit-diff (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) rn))
      (format "%.2f" (comp-increase-diff (parse (.getText p)) (parse (.getText r)) (parse (.getText n)) rn))
       rn)
  (doto dm 
     (.setNumRows (inc (parse (.getText t)))) 
     (.setColumnCount 6)
      (.setValueAt "0.000" 1 4)
      (.fireTableCellUpdated 1 4)
      (.setValueAt "0.00" 1 5)
      (.fireTableCellUpdated 1 5))
 )    
)
:else (do
         (.setText tl (str "Incorrect"))
         (.setText pl (str "Incorrect"))
         (.setForeground tl (Color. 246 15 15))
         (.setForeground pl (Color. 246 15 15))

         (doseq [rn (range 1 15)] 
             (tab-size dm 15 (str "") (str "") (str "") (str "") (str "") (str "") rn)
         )
      )
 )
)

(defn month-con
"The time-label converter inside the application, if the text inside the number-label is 12."
 [a]
 (if (= a 12) 
(str "Number of months:")
(str "Number of years:")
 )
)

(defn update-label [source target convert]
  (when (.isFocusOwner source)
    (if-let [n (parse (.getText source))]
      (.setText target (convert n))
      (.setText target "Number of years:"))))

(defn display-label [source target f]
  (.. source getDocument
      (addDocumentListener
       (proxy [DocumentListener] []
         (insertUpdate [e] (update-label source target f))
         (removeUpdate [e] (update-label source target f))
         (changedUpdate [e] )))))

(defmacro set-grid! [constraints field value]
  `(set! (. ~constraints ~(symbol (name field)))
         ~(if (keyword? value)
            `(. java.awt.GridBagConstraints
                ~(symbol (name value)))
            value)))

(defmacro grid-bag-layout [container & body]
  (let [c (gensym "c")
        cntr (gensym "cntr")]
    `(let [~c (new java.awt.GridBagConstraints)
           ~cntr ~container]
       ~@(loop [result '() body body]
           (if (empty? body)
             (reverse result)
             (let [expr (first body)]
               (if (keyword? expr)
                 (recur (cons `(set-grid! ~c ~expr
                                          ~(second body))
                              result)
                        (next (next body)))
                 (recur (cons `(.add ~cntr ~expr ~c)
                              result)
                        (next body)))))))))


(defn -main
"Financial calculator for Regular Compound Interest."
 []
  (let [rown (int 1)
amount-label (JLabel. "Principal amount:")
        amount-field (JTextField. "" 10) 
        rate-label (JLabel. "Intrest Rate (%):")
        rate-field (JTextField. "" 10) 
        number-label (JLabel. "Times per year:")
        number-field (JTextField. "" 10) 
        times-label (JLabel. "Number of years:")
        times-field (JTextField. "" 10)
        total-label (doto (JLabel. "Total:")
                          (.setFont (Font. "Helvetica" Font/ITALIC, 14)))
        total-label-am (JLabel. "0.00")
        profit-label (doto (JLabel. "Profit (%):")
                           (.setFont (Font. "Helvetica" Font/ITALIC, 14)))
        profit-label-am (JLabel. "0.00")
        dm (DefaultTableModel.) 
        table (JTable. dm)
        menuBar (doto (JMenuBar.)
                  (.add (doto (JMenu. "File")
                    (.add (doto (JMenuItem. "New")
                        (with-action e
                             (doseq [rn (range 1 15)] 
                               (tab-size dm 15 (str "") (str "") (str "") (str "") (str "") (str "") rn)
                              )
                             (.setText amount-field "")
                             (.setText rate-field "")
                             (.setText number-field "")
                             (.setText times-field "")
                             (.setText total-label-am (str "0.00"))
                             (.setText profit-label-am (str "0.00"))
                             (.setForeground total-label-am (Color. 10 10 10))
                             (.setForeground profit-label-am (Color. 10 10 10))
                         )))

                    (.add (doto (JMenuItem. "Last correct entry")
                        (with-action e
                           (if (and (= 0 @am-ref) (= 0 @rt-ref) (= 0 @nm-ref) (= 0 @tm-ref))
                            (message-box "You have not performed any calculations yet.")
                             (do
                             (.setText amount-field (str @am-ref))
                             (.setText rate-field (str @rt-ref))
                             (.setText number-field (reverse-parse @nm-ref))
                             (.setText times-field (reverse-parse @tm-ref))
                             (total-message dm amount-field rate-field number-field times-field total-label-am profit-label-am rown)
                             )
                            )

                        )))

))
                  (.add (doto (JMenu. "About")
                    (.add (doto (JMenuItem. "Help")
                        (with-action e
                           (message-box "Vladimir Tolmacev 80/2010")
                        )))
))

)
        greet-button (JButton. "Calculate")
        panel (doto (JPanel. (GridBagLayout.))
(.setPreferredSize (new Dimension 800 600))                
(grid-bag-layout
                 :insets (Insets. 5 5 5 5)

                 :gridx 0, :gridy 0, :anchor :LINE_END
                 amount-label
                 :gridy 1
                 rate-label
                 :gridy 2
                 number-label
                 :gridy 3
                 times-label
                 :gridy 4
                 total-label
                 :gridy 5
                 profit-label

                 :gridx 1, :gridy 0, :anchor :LINE_START
                 amount-field
                 :gridy 1
                 rate-field
                 :gridy 2
                 number-field
                 :gridy 3
                 times-field
                 :gridy 4
                 total-label-am
                 :gridy 5
                 profit-label-am

                 :gridx 0, :gridy 6, :gridwidth 2, :anchor :CENTER
                 greet-button

                 :gridx 2, :gridy 0, :gridheight 8, :anchor :CENTER
                 table
))]

 (tab-size dm 15 (str "Time") (str "Total") (str "Profit") (str "Increase (%)") (str "Profit (diff)") (str "Inc-diff (%)") 0)

(display-label number-field times-label month-con)

(with-action greet-button e
         (total-message dm amount-field rate-field number-field times-field total-label-am profit-label-am rown)

)

    (doto (JFrame. "Financial calculator")
      (.setJMenuBar menuBar)
      (.setContentPane panel)
      (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
      (.pack)
      (.setVisible true))

))